/*******************************************************************************
 * Copyright 2011, 2012 Chris Banes.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.handmark.pulltorefresh.library;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import com.handmark.pulltorefresh.library.internal.FlipLoadingLayout;
import com.handmark.pulltorefresh.library.internal.LoadingLayout;
import com.handmark.pulltorefresh.library.internal.RotateLoadingLayout;
import com.handmark.pulltorefresh.library.internal.Utils;
import com.handmark.pulltorefresh.library.internal.ViewCompat;

public abstract class PullToRefreshBase<T extends View> extends LinearLayout
	implements IPullToRefresh<T> {

    // ===========================================================
    // Constants
    // ===========================================================

    static final boolean DEBUG = false;

    static final boolean USE_HW_LAYERS = true;

    static final String LOG_TAG = "PullToRefresh";

    static final float FRICTION = 2.0f;

    public static final int SMOOTH_SCROLL_DURATION_MS = 200;
    public static final int SMOOTH_SCROLL_LONG_DURATION_MS = 325;
    static final int DEMO_SCROLL_INTERVAL = 225;

    static final String STATE_STATE = "ptr_state";
    static final String STATE_MODE = "ptr_mode";
    static final String STATE_CURRENT_MODE = "ptr_current_mode";
    static final String STATE_SCROLLING_REFRESHING_ENABLED = "ptr_disable_scrolling";
    static final String STATE_SHOW_REFRESHING_VIEW = "ptr_show_refreshing_view";
    static final String STATE_SUPER = "ptr_super";

    // ===========================================================
    // Fields
    // ===========================================================

    private int mTouchSlop;
    private float mLastMotionX, mLastMotionY;
    private float mInitialMotionX, mInitialMotionY;

    private boolean mIsBeingDragged = false;
    private State mState = State.RESET;
    private Mode mMode = Mode.getDefault();

    private Mode mCurrentMode;
    protected T mRefreshableView;
    private FrameLayout mRefreshableViewWrapper;

    private boolean mShowViewWhileRefreshing = true;
    private boolean mScrollingWhileRefreshingEnabled = false;
    private boolean mFilterTouchEvents = true;
    private boolean mOverScrollEnabled = true;
    private boolean mLayoutVisibilityChangesEnabled = true;

    private Interpolator mScrollAnimationInterpolator;
    private AnimationStyle mLoadingAnimationStyle = AnimationStyle.getDefault();

    private LoadingLayout mHeaderLayout;
    private LoadingLayout mFooterLayout;

    private OnRefreshListener<T> mOnRefreshListener;
    private OnRefreshListener2<T> mOnRefreshListener2;
    private OnPullEventListener<T> mOnPullEventListener;

    private SmoothScrollRunnable mCurrentSmoothScrollRunnable;

    // ===========================================================
    // Constructors
    // ===========================================================

    public PullToRefreshBase(Context context) {
	super(context);
	init(context, null);
    }

    public PullToRefreshBase(Context context, AttributeSet attrs) {
	super(context, attrs);
	init(context, attrs);
    }

    public PullToRefreshBase(Context context, Mode mode) {
	super(context);
	mMode = mode;
	init(context, null);
    }

    public PullToRefreshBase(Context context, Mode mode,
	    AnimationStyle animStyle) {
	super(context);
	mMode = mode;
	mLoadingAnimationStyle = animStyle;
	init(context, null);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
	if (DEBUG) {
	    Log.d(LOG_TAG, "addView: " + child.getClass().getSimpleName());
	}

	final T refreshableView = getRefreshableView();

	if (refreshableView instanceof ViewGroup) {
	    ((ViewGroup) refreshableView).addView(child, index, params);
	} else {
	    throw new UnsupportedOperationException(
		    "Refreshable View is not a ViewGroup so can't addView");
	}
    }

    @Override
    public final boolean demo() {
	if (mMode.showHeaderLoadingLayout() && isReadyForPullStart()) {
	    smoothScrollToAndBack(-getHeaderSize() * 2);
	    return true;
	} else if (mMode.showFooterLoadingLayout() && isReadyForPullEnd()) {
	    smoothScrollToAndBack(getFooterSize() * 2);
	    return true;
	}

	return false;
    }

    @Override
    public final Mode getCurrentMode() {
	return mCurrentMode;
    }

    @Override
    public final boolean getFilterTouchEvents() {
	return mFilterTouchEvents;
    }

    @Override
    public final ILoadingLayout getLoadingLayoutProxy() {
	return getLoadingLayoutProxy(true, true);
    }

    @Override
    public final ILoadingLayout getLoadingLayoutProxy(boolean includeStart,
	    boolean includeEnd) {
	return createLoadingLayoutProxy(includeStart, includeEnd);
    }

    @Override
    public final Mode getMode() {
	return mMode;
    }

    @Override
    public final T getRefreshableView() {
	return mRefreshableView;
    }

    @Override
    public final boolean getShowViewWhileRefreshing() {
	return mShowViewWhileRefreshing;
    }

    @Override
    public final State getState() {
	return mState;
    }

    /**
     * @deprecated See {@link #isScrollingWhileRefreshingEnabled()}.
     */
    public final boolean isDisableScrollingWhileRefreshing() {
	return !isScrollingWhileRefreshingEnabled();
    }

    @Override
    public final boolean isPullToRefreshEnabled() {
	return mMode.permitsPullToRefresh();
    }

    @Override
    public final boolean isPullToRefreshOverScrollEnabled() {
	return VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD
		&& mOverScrollEnabled
		&& OverscrollHelper
			.isAndroidOverScrollEnabled(mRefreshableView);
    }

    @Override
    public final boolean isRefreshing() {
	return mState == State.REFRESHING || mState == State.MANUAL_REFRESHING;
    }

    @Override
    public final boolean isScrollingWhileRefreshingEnabled() {
	return mScrollingWhileRefreshingEnabled;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

	if (!isPullToRefreshEnabled()) {
	    return false;
	}

	final int action = event.getAction();

	if (action == MotionEvent.ACTION_CANCEL
		|| action == MotionEvent.ACTION_UP) {
	    mIsBeingDragged = false;
	    return false;
	}

	if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
	    return true;
	}

	switch (action) {
	case MotionEvent.ACTION_MOVE: {
	    // If we're refreshing, and the flag is set. Eat all MOVE events
	    if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
		return true;
	    }

	    if (isReadyForPull()) {
		final float y = event.getY(), x = event.getX();
		final float diff, oppositeDiff, absDiff;

		// We need to use the correct values, based on scroll
		// direction
		switch (getPullToRefreshScrollDirection()) {
		case HORIZONTAL:
		    diff = x - mLastMotionX;
		    oppositeDiff = y - mLastMotionY;
		    break;
		case VERTICAL:
		default:
		    diff = y - mLastMotionY;
		    oppositeDiff = x - mLastMotionX;
		    break;
		}
		absDiff = Math.abs(diff);

		if (absDiff > mTouchSlop
			&& (!mFilterTouchEvents || absDiff > Math
				.abs(oppositeDiff))) {
		    if (mMode.showHeaderLoadingLayout() && diff >= 1f
			    && isReadyForPullStart()) {
			mLastMotionY = y;
			mLastMotionX = x;
			mIsBeingDragged = true;
			if (mMode == Mode.BOTH) {
			    mCurrentMode = Mode.PULL_FROM_START;
			}
		    } else if (mMode.showFooterLoadingLayout() && diff <= -1f
			    && isReadyForPullEnd()) {
			mLastMotionY = y;
			mLastMotionX = x;
			mIsBeingDragged = true;
			if (mMode == Mode.BOTH) {
			    mCurrentMode = Mode.PULL_FROM_END;
			}
		    }
		}
	    }
	    break;
	}
	case MotionEvent.ACTION_DOWN: {
	    if (isReadyForPull()) {
		mLastMotionY = mInitialMotionY = event.getY();
		mLastMotionX = mInitialMotionX = event.getX();
		mIsBeingDragged = false;
	    }
	    break;
	}
	}

	return mIsBeingDragged;
    }

    @Override
    public final void onRefreshComplete() {
	if (isRefreshing()) {
	    setState(State.RESET);
	}
    }

    @Override
    public final boolean onTouchEvent(MotionEvent event) {

	if (!isPullToRefreshEnabled()) {
	    return false;
	}

	// If we're refreshing, and the flag is set. Eat the event
	if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
	    return true;
	}

	if (event.getAction() == MotionEvent.ACTION_DOWN
		&& event.getEdgeFlags() != 0) {
	    return false;
	}

	switch (event.getAction()) {
	case MotionEvent.ACTION_MOVE: {
	    if (mIsBeingDragged) {
		mLastMotionY = event.getY();
		mLastMotionX = event.getX();
		pullEvent();
		return true;
	    }
	    break;
	}

	case MotionEvent.ACTION_DOWN: {
	    if (isReadyForPull()) {
		mLastMotionY = mInitialMotionY = event.getY();
		mLastMotionX = mInitialMotionX = event.getX();
		return true;
	    }
	    break;
	}

	case MotionEvent.ACTION_CANCEL:
	case MotionEvent.ACTION_UP: {
	    if (mIsBeingDragged) {
		mIsBeingDragged = false;

		if (mState == State.RELEASE_TO_REFRESH
			&& (null != mOnRefreshListener || null != mOnRefreshListener2)) {
		    setState(State.REFRESHING, true);
		    return true;
		}

		// If we're already refreshing, just scroll back to the top
		if (isRefreshing()) {
		    smoothScrollTo(0);
		    return true;
		}

		// If we haven't returned by here, then we're not in a state
		// to pull, so just reset
		setState(State.RESET);

		return true;
	    }
	    break;
	}
	}

	return false;
    }

    public final void setScrollingWhileRefreshingEnabled(
	    boolean allowScrollingWhileRefreshing) {
	mScrollingWhileRefreshingEnabled = allowScrollingWhileRefreshing;
    }

    /**
     * @deprecated See {@link #setScrollingWhileRefreshingEnabled(boolean)}
     */
    public void setDisableScrollingWhileRefreshing(
	    boolean disableScrollingWhileRefreshing) {
	setScrollingWhileRefreshingEnabled(!disableScrollingWhileRefreshing);
    }

    @Override
    public final void setFilterTouchEvents(boolean filterEvents) {
	mFilterTouchEvents = filterEvents;
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy()}.
     */
    public void setLastUpdatedLabel(CharSequence label) {
	getLoadingLayoutProxy().setLastUpdatedLabel(label);
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy()}.
     */
    public void setLoadingDrawable(Drawable drawable) {
	getLoadingLayoutProxy().setLoadingDrawable(drawable);
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy(boolean, boolean)}.
     */
    public void setLoadingDrawable(Drawable drawable, Mode mode) {
	getLoadingLayoutProxy(mode.showHeaderLoadingLayout(),
		mode.showFooterLoadingLayout()).setLoadingDrawable(drawable);
    }

    @Override
    public void setLongClickable(boolean longClickable) {
	getRefreshableView().setLongClickable(longClickable);
    }

    @Override
    public final void setMode(Mode mode) {
	if (mode != mMode) {
	    if (DEBUG) {
		Log.d(LOG_TAG, "Setting mode to: " + mode);
	    }
	    mMode = mode;
	    updateUIForMode();
	}
    }

    public void setOnPullEventListener(OnPullEventListener<T> listener) {
	mOnPullEventListener = listener;
    }

    @Override
    public final void setOnRefreshListener(OnRefreshListener<T> listener) {
	mOnRefreshListener = listener;
	mOnRefreshListener2 = null;
    }

    @Override
    public final void setOnRefreshListener(OnRefreshListener2<T> listener) {
	mOnRefreshListener2 = listener;
	mOnRefreshListener = null;
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy()}.
     */
    public void setPullLabel(CharSequence pullLabel) {
	getLoadingLayoutProxy().setPullLabel(pullLabel);
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy(boolean, boolean)}.
     */
    public void setPullLabel(CharSequence pullLabel, Mode mode) {
	getLoadingLayoutProxy(mode.showHeaderLoadingLayout(),
		mode.showFooterLoadingLayout()).setPullLabel(pullLabel);
    }

    /**
     * @param enable
     *            Whether Pull-To-Refresh should be used
     * @deprecated This simple calls setMode with an appropriate mode based on
     *             the passed value.
     */
    public final void setPullToRefreshEnabled(boolean enable) {
	setMode(enable ? Mode.getDefault() : Mode.DISABLED);
    }

    @Override
    public final void setPullToRefreshOverScrollEnabled(boolean enabled) {
	mOverScrollEnabled = enabled;
    }

    @Override
    public final void setRefreshing() {
	setRefreshing(true);
    }

    @Override
    public final void setRefreshing(boolean doScroll) {
	if (!isRefreshing()) {
	    mCurrentMode = Mode.PULL_FROM_START;
	    setState(State.MANUAL_REFRESHING, doScroll);
	}
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy()}.
     */
    public void setRefreshingLabel(CharSequence refreshingLabel) {
	getLoadingLayoutProxy().setRefreshingLabel(refreshingLabel);
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy(boolean, boolean)}.
     */
    public void setRefreshingLabel(CharSequence refreshingLabel, Mode mode) {
	getLoadingLayoutProxy(mode.showHeaderLoadingLayout(),
		mode.showFooterLoadingLayout()).setRefreshingLabel(
		refreshingLabel);
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy()}.
     */
    public void setReleaseLabel(CharSequence releaseLabel) {
	setReleaseLabel(releaseLabel, Mode.BOTH);
    }

    /**
     * @deprecated You should now call this method on the result of
     *             {@link #getLoadingLayoutProxy(boolean, boolean)}.
     */
    public void setReleaseLabel(CharSequence releaseLabel, Mode mode) {
	getLoadingLayoutProxy(mode.showHeaderLoadingLayout(),
		mode.showFooterLoadingLayout()).setReleaseLabel(releaseLabel);
    }

    public void setScrollAnimationInterpolator(Interpolator interpolator) {
	mScrollAnimationInterpolator = interpolator;
    }

    @Override
    public final void setShowViewWhileRefreshing(boolean showView) {
	mShowViewWhileRefreshing = showView;
    }

    /**
     * @return Either {@link Orientation#VERTICAL} or
     *         {@link Orientation#HORIZONTAL} depending on the scroll direction.
     */
    public abstract Orientation getPullToRefreshScrollDirection();

    final void setState(State state, final boolean... params) {
	mState = state;
	if (DEBUG) {
	    Log.d(LOG_TAG, "State: " + mState.name());
	}
	switch (mState) {
	case RESET:
	    onReset();
	    break;
	case PULL_TO_REFRESH:
	    onPullToRefresh();
	    break;
	case RELEASE_TO_REFRESH:
	    onReleaseToRefresh();
	    break;
	case REFRESHING:
	case MANUAL_REFRESHING:
	    onRefreshing(params[0]);
	    break;
	case OVERSCROLLING:
	    // NO-OP
	    break;
	}

	// Call OnPullEventListener
	if (null != mOnPullEventListener) {
	    mOnPullEventListener.onPullEvent(this, mState, mCurrentMode);
	}
    }

    /**
     * Used internally for adding view. Need because we override addView to
     * pass-through to the Refreshable View
     */
    protected final void addViewInternal(View child, int index,
	    ViewGroup.LayoutParams params) {
	super.addView(child, index, params);
    }

    /**
     * Used internally for adding view. Need because we override addView to
     * pass-through to the Refreshable View
     */
    protected final void addViewInternal(View child,
	    ViewGroup.LayoutParams params) {
	super.addView(child, -1, params);
    }

    protected LoadingLayout createLoadingLayout(Context context, Mode mode,
	    TypedArray attrs) {
	LoadingLayout layout = mLoadingAnimationStyle.createLoadingLayout(
		context, mode, getPullToRefreshScrollDirection(), attrs);
	layout.setVisibility(View.INVISIBLE);
	return layout;
    }

    /**
     * Used internally for {@link #getLoadingLayoutProxy(boolean, boolean)}.
     * Allows derivative classes to include any extra LoadingLayouts.
     */
    protected LoadingLayoutProxy createLoadingLayoutProxy(
	    final boolean includeStart, final boolean includeEnd) {
	LoadingLayoutProxy proxy = new LoadingLayoutProxy();

	if (includeStart && mMode.showHeaderLoadingLayout()) {
	    proxy.addLayout(mHeaderLayout);
	}
	if (includeEnd && mMode.showFooterLoadingLayout()) {
	    proxy.addLayout(mFooterLayout);
	}

	return proxy;
    }

    /**
     * This is implemented by derived classes to return the created View. If you
     * need to use a custom View (such as a custom ListView), override this
     * method and return an instance of your custom class.
     * <p/>
     * Be sure to set the ID of the view in this method, especially if you're
     * using a ListActivity or ListFragment.
     * 
     * @param context
     *            Context to create view with
     * @param attrs
     *            AttributeSet from wrapped class. Means that anything you
     *            include in the XML layout declaration will be routed to the
     *            created View
     * @return New instance of the Refreshable View
     */
    protected abstract T createRefreshableView(Context context,
	    AttributeSet attrs);

    protected final void disableLoadingLayoutVisibilityChanges() {
	mLayoutVisibilityChangesEnabled = false;
    }

    protected final LoadingLayout getFooterLayout() {
	return mFooterLayout;
    }

    protected final int getFooterSize() {
	return mFooterLayout.getContentSize();
    }

    protected final LoadingLayout getHeaderLayout() {
	return mHeaderLayout;
    }

    protected final int getHeaderSize() {
	return mHeaderLayout.getContentSize();
    }

    protected int getPullToRefreshScrollDuration() {
	return SMOOTH_SCROLL_DURATION_MS;
    }

    protected int getPullToRefreshScrollDurationLonger() {
	return SMOOTH_SCROLL_LONG_DURATION_MS;
    }

    protected FrameLayout getRefreshableViewWrapper() {
	return mRefreshableViewWrapper;
    }

    /**
     * Allows Derivative classes to handle the XML Attrs without creating a
     * TypedArray themsevles
     * 
     * @param a
     *            - TypedArray of PullToRefresh Attributes
     */
    protected void handleStyledAttributes(TypedArray a) {
    }

    /**
     * Implemented by derived class to return whether the View is in a state
     * where the user can Pull to Refresh by scrolling from the end.
     * 
     * @return true if the View is currently in the correct state (for example,
     *         bottom of a ListView)
     */
    protected abstract boolean isReadyForPullEnd();

    /**
     * Implemented by derived class to return whether the View is in a state
     * where the user can Pull to Refresh by scrolling from the start.
     * 
     * @return true if the View is currently the correct state (for example, top
     *         of a ListView)
     */
    protected abstract boolean isReadyForPullStart();

    /**
     * Called by {@link #onRestoreInstanceState(Parcelable)} so that derivative
     * classes can handle their saved instance state.
     * 
     * @param savedInstanceState
     *            - Bundle which contains saved instance state.
     */
    protected void onPtrRestoreInstanceState(Bundle savedInstanceState) {
    }

    /**
     * Called by {@link #onSaveInstanceState()} so that derivative classes can
     * save their instance state.
     * 
     * @param saveState
     *            - Bundle to be updated with saved state.
     */
    protected void onPtrSaveInstanceState(Bundle saveState) {
    }

    /**
     * Called when the UI has been to be updated to be in the
     * {@link State#PULL_TO_REFRESH} state.
     */
    protected void onPullToRefresh() {
	switch (mCurrentMode) {
	case PULL_FROM_END:
	    mFooterLayout.pullToRefresh();
	    break;
	case PULL_FROM_START:
	    mHeaderLayout.pullToRefresh();
	    break;
	default:
	    // NO-OP
	    break;
	}
    }

    /**
     * Called when the UI has been to be updated to be in the
     * {@link State#REFRESHING} or {@link State#MANUAL_REFRESHING} state.
     * 
     * @param doScroll
     *            - Whether the UI should scroll for this event.
     */
    protected void onRefreshing(final boolean doScroll) {
	if (mMode.showHeaderLoadingLayout()) {
	    mHeaderLayout.refreshing();
	}
	if (mMode.showFooterLoadingLayout()) {
	    mFooterLayout.refreshing();
	}

	if (doScroll) {
	    if (mShowViewWhileRefreshing) {

		// Call Refresh Listener when the Scroll has finished
		OnSmoothScrollFinishedListener listener = new OnSmoothScrollFinishedListener() {
		    @Override
		    public void onSmoothScrollFinished() {
			callRefreshListener();
		    }
		};

		switch (mCurrentMode) {
		case MANUAL_REFRESH_ONLY:
		case PULL_FROM_END:
		    smoothScrollTo(getFooterSize(), listener);
		    break;
		default:
		case PULL_FROM_START:
		    smoothScrollTo(-getHeaderSize(), listener);
		    break;
		}
	    } else {
		smoothScrollTo(0);
	    }
	} else {
	    // We're not scrolling, so just call Refresh Listener now
	    callRefreshListener();
	}
    }

    /**
     * Called when the UI has been to be updated to be in the
     * {@link State#RELEASE_TO_REFRESH} state.
     */
    protected void onReleaseToRefresh() {
	switch (mCurrentMode) {
	case PULL_FROM_END:
	    mFooterLayout.releaseToRefresh();
	    break;
	case PULL_FROM_START:
	    mHeaderLayout.releaseToRefresh();
	    break;
	default:
	    // NO-OP
	    break;
	}
    }

    /**
     * Called when the UI has been to be updated to be in the
     * {@link State#RESET} state.
     */
    protected void onReset() {
	mIsBeingDragged = false;
	mLayoutVisibilityChangesEnabled = true;

	// Always reset both layouts, just in case...
	mHeaderLayout.reset();
	mFooterLayout.reset();

	smoothScrollTo(0);
    }

    @Override
    protected final void onRestoreInstanceState(Parcelable state) {
	if (state instanceof Bundle) {
	    Bundle bundle = (Bundle) state;

	    // setMode(Mode.mapIntToValue(bundle.getInt(STATE_MODE, 0)));
	    // mCurrentMode =
	    // Mode.mapIntToValue(bundle.getInt(STATE_CURRENT_MODE,
	    // 0));
	    //
	    // mScrollingWhileRefreshingEnabled = bundle.getBoolean(
	    // STATE_SCROLLING_REFRESHING_ENABLED, false);
	    // mShowViewWhileRefreshing = bundle.getBoolean(
	    // STATE_SHOW_REFRESHING_VIEW, true);
	    //
	    // // Let super Restore Itself
	    super.onRestoreInstanceState(bundle.getParcelable(STATE_SUPER));
	    //
	    // State viewState = State
	    // .mapIntToValue(bundle.getInt(STATE_STATE, 0));
	    // if (viewState == State.REFRESHING
	    // || viewState == State.MANUAL_REFRESHING) {
	    // setState(viewState, true);
	    // }

	    // Now let derivative classes restore their state
	    onPtrRestoreInstanceState(bundle);
	    return;
	}

	super.onRestoreInstanceState(state);
    }

    @Override
    protected final Parcelable onSaveInstanceState() {
	Bundle bundle = new Bundle();

	// Let derivative classes get a chance to save state first, that way we
	// can make sure they don't overrite any of our values
	onPtrSaveInstanceState(bundle);
	//
	// bundle.putInt(STATE_STATE, mState.getIntValue());
	// bundle.putInt(STATE_MODE, mMode.getIntValue());
	// bundle.putInt(STATE_CURRENT_MODE, mCurrentMode.getIntValue());
	// bundle.putBoolean(STATE_SCROLLING_REFRESHING_ENABLED,
	// mScrollingWhileRefreshingEnabled);
	// bundle.putBoolean(STATE_SHOW_REFRESHING_VIEW,
	// mShowViewWhileRefreshing);
	bundle.putParcelable(STATE_SUPER, super.onSaveInstanceState());

	return bundle;
    }

    @Override
    protected final void onSizeChanged(int w, int h, int oldw, int oldh) {
	if (DEBUG) {
	    Log.d(LOG_TAG, String.format("onSizeChanged. W: %d, H: %d", w, h));
	}

	super.onSizeChanged(w, h, oldw, oldh);

	// We need to update the header/footer when our size changes
	refreshLoadingViewsSize();

	// Update the Refreshable View layout
	refreshRefreshableViewSize(w, h);

	/**
	 * As we're currently in a Layout Pass, we need to schedule another one
	 * to layout any changes we've made here
	 */
	post(new Runnable() {
	    @Override
	    public void run() {
		requestLayout();
	    }
	});
    }

    /**
     * Re-measure the Loading Views height, and adjust internal padding as
     * necessary
     */
    protected final void refreshLoadingViewsSize() {
	final int maximumPullScroll = (int) (getMaximumPullScroll() * 1.2f);

	int pLeft = getPaddingLeft();
	int pTop = getPaddingTop();
	int pRight = getPaddingRight();
	int pBottom = getPaddingBottom();

	switch (getPullToRefreshScrollDirection()) {
	case HORIZONTAL:
	    if (mMode.showHeaderLoadingLayout()) {
		mHeaderLayout.setWidth(maximumPullScroll);
		pLeft = -maximumPullScroll;
	    } else {
		pLeft = 0;
	    }

	    if (mMode.showFooterLoadingLayout()) {
		mFooterLayout.setWidth(maximumPullScroll);
		pRight = -maximumPullScroll;
	    } else {
		pRight = 0;
	    }
	    break;

	case VERTICAL:
	    if (mMode.showHeaderLoadingLayout()) {
		mHeaderLayout.setHeight(maximumPullScroll);
		pTop = -maximumPullScroll;
	    } else {
		pTop = 0;
	    }

	    if (mMode.showFooterLoadingLayout()) {
		mFooterLayout.setHeight(maximumPullScroll);
		pBottom = -maximumPullScroll;
	    } else {
		pBottom = 0;
	    }
	    break;
	}

	if (DEBUG) {
	    Log.d(LOG_TAG, String.format(
		    "Setting Padding. L: %d, T: %d, R: %d, B: %d", pLeft, pTop,
		    pRight, pBottom));
	}
	setPadding(pLeft, pTop, pRight, pBottom);
    }

    protected final void refreshRefreshableViewSize(int width, int height) {
	// We need to set the Height of the Refreshable View to the same as
	// this layout
	LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mRefreshableViewWrapper
		.getLayoutParams();

	switch (getPullToRefreshScrollDirection()) {
	case HORIZONTAL:
	    if (lp.width != width) {
		lp.width = width;
		mRefreshableViewWrapper.requestLayout();
	    }
	    break;
	case VERTICAL:
	    if (lp.height != height) {
		lp.height = height;
		mRefreshableViewWrapper.requestLayout();
	    }
	    break;
	}
    }

    /**
     * Helper method which just calls scrollTo() in the correct scrolling
     * direction.
     * 
     * @param value
     *            - New Scroll value
     */
    protected final void setHeaderScroll(int value) {
	if (DEBUG) {
	    Log.d(LOG_TAG, "setHeaderScroll: " + value);
	}

	// Clamp value to with pull scroll range
	final int maximumPullScroll = getMaximumPullScroll();
	value = Math
		.min(maximumPullScroll, Math.max(-maximumPullScroll, value));

	if (mLayoutVisibilityChangesEnabled) {
	    if (value < 0) {
		mHeaderLayout.setVisibility(View.VISIBLE);
	    } else if (value > 0) {
		mFooterLayout.setVisibility(View.VISIBLE);
	    } else {
		mHeaderLayout.setVisibility(View.INVISIBLE);
		mFooterLayout.setVisibility(View.INVISIBLE);
	    }
	}

	if (USE_HW_LAYERS) {
	    /**
	     * Use a Hardware Layer on the Refreshable View if we've scrolled at
	     * all. We don't use them on the Header/Footer Views as they change
	     * often, which would negate any HW layer performance boost.
	     */
	    ViewCompat.setLayerType(mRefreshableViewWrapper,
		    value != 0 ? View.LAYER_TYPE_HARDWARE
			    : View.LAYER_TYPE_NONE);
	}

	switch (getPullToRefreshScrollDirection()) {
	case VERTICAL:
	    scrollTo(0, value);
	    break;
	case HORIZONTAL:
	    scrollTo(value, 0);
	    break;
	}
    }

    /**
     * Smooth Scroll to position using the default duration of
     * {@value #SMOOTH_SCROLL_DURATION_MS} ms.
     * 
     * @param scrollValue
     *            - Position to scroll to
     */
    protected final void smoothScrollTo(int scrollValue) {
	smoothScrollTo(scrollValue, getPullToRefreshScrollDuration());
    }

    /**
     * Smooth Scroll to position using the default duration of
     * {@value #SMOOTH_SCROLL_DURATION_MS} ms.
     * 
     * @param scrollValue
     *            - Position to scroll to
     * @param listener
     *            - Listener for scroll
     */
    protected final void smoothScrollTo(int scrollValue,
	    OnSmoothScrollFinishedListener listener) {
	smoothScrollTo(scrollValue, getPullToRefreshScrollDuration(), 0,
		listener);
    }

    /**
     * Smooth Scroll to position using the longer default duration of
     * {@value #SMOOTH_SCROLL_LONG_DURATION_MS} ms.
     * 
     * @param scrollValue
     *            - Position to scroll to
     */
    protected final void smoothScrollToLonger(int scrollValue) {
	smoothScrollTo(scrollValue, getPullToRefreshScrollDurationLonger());
    }

    /**
     * Updates the View State when the mode has been set. This does not do any
     * checking that the mode is different to current state so always updates.
     */
    protected void updateUIForMode() {
	// We need to use the correct LayoutParam values, based on scroll
	// direction
	final LinearLayout.LayoutParams lp = getLoadingLayoutLayoutParams();

	// Remove Header, and then add Header Loading View again if needed
	if (this == mHeaderLayout.getParent()) {
	    removeView(mHeaderLayout);
	}
	if (mMode.showHeaderLoadingLayout()) {
	    addViewInternal(mHeaderLayout, 0, lp);
	}

	// Remove Footer, and then add Footer Loading View again if needed
	if (this == mFooterLayout.getParent()) {
	    removeView(mFooterLayout);
	}
	if (mMode.showFooterLoadingLayout()) {
	    addViewInternal(mFooterLayout, lp);
	}

	// Hide Loading Views
	refreshLoadingViewsSize();

	// If we're not using Mode.BOTH, set mCurrentMode to mMode, otherwise
	// set it to pull down
	mCurrentMode = (mMode != Mode.BOTH) ? mMode : Mode.PULL_FROM_START;
    }

    private void addRefreshableView(Context context, T refreshableView) {
	mRefreshableViewWrapper = new FrameLayout(context);
	mRefreshableViewWrapper.addView(refreshableView,
		ViewGroup.LayoutParams.MATCH_PARENT,
		ViewGroup.LayoutParams.MATCH_PARENT);

	addViewInternal(mRefreshableViewWrapper, new LinearLayout.LayoutParams(
		LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    }

    private void callRefreshListener() {
	if (null != mOnRefreshListener) {
	    mOnRefreshListener.onRefresh(this);
	} else if (null != mOnRefreshListener2) {
	    if (mCurrentMode == Mode.PULL_FROM_START) {
		mOnRefreshListener2.onPullDownToRefresh(this);
	    } else if (mCurrentMode == Mode.PULL_FROM_END) {
		mOnRefreshListener2.onPullUpToRefresh(this);
	    }
	}
    }

    @SuppressWarnings("deprecation")
    private void init(Context context, AttributeSet attrs) {
	switch (getPullToRefreshScrollDirection()) {
	case HORIZONTAL:
	    setOrientation(LinearLayout.HORIZONTAL);
	    break;
	case VERTICAL:
	default:
	    setOrientation(LinearLayout.VERTICAL);
	    break;
	}

	setGravity(Gravity.CENTER);

	ViewConfiguration config = ViewConfiguration.get(context);
	mTouchSlop = config.getScaledTouchSlop();

	// Styleables from XML
	TypedArray a = context.obtainStyledAttributes(attrs,
		R.styleable.PullToRefresh);

	if (a.hasValue(R.styleable.PullToRefresh_ptrMode)) {
	    mMode = Mode.mapIntToValue(a.getInteger(
		    R.styleable.PullToRefresh_ptrMode, 0));
	}

	if (a.hasValue(R.styleable.PullToRefresh_ptrAnimationStyle)) {
	    mLoadingAnimationStyle = AnimationStyle.mapIntToValue(a.getInteger(
		    R.styleable.PullToRefresh_ptrAnimationStyle, 0));
	}

	// Refreshable View
	// By passing the attrs, we can add ListView/GridView params via XML
	mRefreshableView = createRefreshableView(context, attrs);
	addRefreshableView(context, mRefreshableView);

	// We need to create now layouts now
	mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
	mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);

	/**
	 * Styleables from XML
	 */
	if (a.hasValue(R.styleable.PullToRefresh_ptrRefreshableViewBackground)) {
	    Drawable background = a
		    .getDrawable(R.styleable.PullToRefresh_ptrRefreshableViewBackground);
	    if (null != background) {
		mRefreshableView.setBackgroundDrawable(background);
	    }
	} else if (a
		.hasValue(R.styleable.PullToRefresh_ptrAdapterViewBackground)) {
	    Utils.warnDeprecation("ptrAdapterViewBackground",
		    "ptrRefreshableViewBackground");
	    Drawable background = a
		    .getDrawable(R.styleable.PullToRefresh_ptrAdapterViewBackground);
	    if (null != background) {
		mRefreshableView.setBackgroundDrawable(background);
	    }
	}

	if (a.hasValue(R.styleable.PullToRefresh_ptrOverScroll)) {
	    mOverScrollEnabled = a.getBoolean(
		    R.styleable.PullToRefresh_ptrOverScroll, true);
	}

	if (a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
	    mScrollingWhileRefreshingEnabled = a
		    .getBoolean(
			    R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled,
			    false);
	}

	// Let the derivative classes have a go at handling attributes, then
	// recycle them...
	handleStyledAttributes(a);
	a.recycle();

	// Finally update the UI for the modes
	updateUIForMode();
    }

    private boolean isReadyForPull() {
	switch (mMode) {
	case PULL_FROM_START:
	    return isReadyForPullStart();
	case PULL_FROM_END:
	    return isReadyForPullEnd();
	case BOTH:
	    return isReadyForPullEnd() || isReadyForPullStart();
	default:
	    return false;
	}
    }

    /**
     * Actions a Pull Event
     * 
     * @return true if the Event has been handled, false if there has been no
     *         change
     */
    private void pullEvent() {
	final int newScrollValue;
	final int itemDimension;
	final float initialMotionValue, lastMotionValue;

	switch (getPullToRefreshScrollDirection()) {
	case HORIZONTAL:
	    initialMotionValue = mInitialMotionX;
	    lastMotionValue = mLastMotionX;
	    break;
	case VERTICAL:
	default:
	    initialMotionValue = mInitialMotionY;
	    lastMotionValue = mLastMotionY;
	    break;
	}

	switch (mCurrentMode) {
	case PULL_FROM_END:
	    newScrollValue = Math.round(Math.max(initialMotionValue
		    - lastMotionValue, 0)
		    / FRICTION);
	    itemDimension = getFooterSize();
	    break;
	case PULL_FROM_START:
	default:
	    newScrollValue = Math.round(Math.min(initialMotionValue
		    - lastMotionValue, 0)
		    / FRICTION);
	    itemDimension = getHeaderSize();
	    break;
	}

	setHeaderScroll(newScrollValue);

	if (newScrollValue != 0 && !isRefreshing()) {
	    float scale = Math.abs(newScrollValue) / (float) itemDimension;
	    switch (mCurrentMode) {
	    case PULL_FROM_END:
		mFooterLayout.onPull(scale);
		break;
	    case PULL_FROM_START:
	    default:
		mHeaderLayout.onPull(scale);
		break;
	    }

	    if (mState != State.PULL_TO_REFRESH
		    && itemDimension >= Math.abs(newScrollValue)) {
		setState(State.PULL_TO_REFRESH);
	    } else if (mState == State.PULL_TO_REFRESH
		    && itemDimension < Math.abs(newScrollValue)) {
		setState(State.RELEASE_TO_REFRESH);
	    }
	}
    }

    private LinearLayout.LayoutParams getLoadingLayoutLayoutParams() {
	switch (getPullToRefreshScrollDirection()) {
	case HORIZONTAL:
	    return new LinearLayout.LayoutParams(
		    LinearLayout.LayoutParams.WRAP_CONTENT,
		    LinearLayout.LayoutParams.MATCH_PARENT);
	case VERTICAL:
	default:
	    return new LinearLayout.LayoutParams(
		    LinearLayout.LayoutParams.MATCH_PARENT,
		    LinearLayout.LayoutParams.WRAP_CONTENT);
	}
    }

    private int getMaximumPullScroll() {
	switch (getPullToRefreshScrollDirection()) {
	case HORIZONTAL:
	    return Math.round(getWidth() / FRICTION);
	case VERTICAL:
	default:
	    return Math.round(getHeight() / FRICTION);
	}
    }

    /**
     * Smooth Scroll to position using the specific duration
     * 
     * @param scrollValue
     *            - Position to scroll to
     * @param duration
     *            - Duration of animation in milliseconds
     */
    private final void smoothScrollTo(int scrollValue, long duration) {
	smoothScrollTo(scrollValue, duration, 0, null);
    }

    private final void smoothScrollTo(int newScrollValue, long duration,
	    long delayMillis, OnSmoothScrollFinishedListener listener) {
	if (null != mCurrentSmoothScrollRunnable) {
	    mCurrentSmoothScrollRunnable.stop();
	}

	final int oldScrollValue;
	switch (getPullToRefreshScrollDirection()) {
	case HORIZONTAL:
	    oldScrollValue = getScrollX();
	    break;
	case VERTICAL:
	default:
	    oldScrollValue = getScrollY();
	    break;
	}

	if (oldScrollValue != newScrollValue) {
	    if (null == mScrollAnimationInterpolator) {
		// Default interpolator is a Decelerate Interpolator
		mScrollAnimationInterpolator = new DecelerateInterpolator();
	    }
	    mCurrentSmoothScrollRunnable = new SmoothScrollRunnable(
		    oldScrollValue, newScrollValue, duration, listener);

	    if (delayMillis > 0) {
		postDelayed(mCurrentSmoothScrollRunnable, delayMillis);
	    } else {
		post(mCurrentSmoothScrollRunnable);
	    }
	}
    }

    private final void smoothScrollToAndBack(int y) {
	smoothScrollTo(y, SMOOTH_SCROLL_DURATION_MS, 0,
		new OnSmoothScrollFinishedListener() {

		    @Override
		    public void onSmoothScrollFinished() {
			smoothScrollTo(0, SMOOTH_SCROLL_DURATION_MS,
				DEMO_SCROLL_INTERVAL, null);
		    }
		});
    }

    public static enum AnimationStyle {
	/**
	 * This is the default for Android-PullToRefresh. Allows you to use any
	 * drawable, which is automatically rotated and used as a Progress Bar.
	 */
	ROTATE,

	/**
	 * This is the old default, and what is commonly used on iOS. Uses an
	 * arrow image which flips depending on where the user has scrolled.
	 */
	FLIP;

	static AnimationStyle getDefault() {
	    return ROTATE;
	}

	/**
	 * Maps an int to a specific mode. This is needed when saving state, or
	 * inflating the view from XML where the mode is given through a attr
	 * int.
	 * 
	 * @param modeInt
	 *            - int to map a Mode to
	 * @return Mode that modeInt maps to, or ROTATE by default.
	 */
	static AnimationStyle mapIntToValue(int modeInt) {
	    switch (modeInt) {
	    case 0x0:
	    default:
		return ROTATE;
	    case 0x1:
		return FLIP;
	    }
	}

	LoadingLayout createLoadingLayout(Context context, Mode mode,
		Orientation scrollDirection, TypedArray attrs) {
	    switch (this) {
	    case ROTATE:
	    default:
		return new RotateLoadingLayout(context, mode, scrollDirection,
			attrs);
	    case FLIP:
		return new FlipLoadingLayout(context, mode, scrollDirection,
			attrs);
	    }
	}
    }

    public static enum Mode {

	/**
	 * Disable all Pull-to-Refresh gesture and Refreshing handling
	 */
	DISABLED(0x0),

	/**
	 * Only allow the user to Pull from the start of the Refreshable View to
	 * refresh. The start is either the Top or Left, depending on the
	 * scrolling direction.
	 */
	PULL_FROM_START(0x1),

	/**
	 * Only allow the user to Pull from the end of the Refreshable View to
	 * refresh. The start is either the Bottom or Right, depending on the
	 * scrolling direction.
	 */
	PULL_FROM_END(0x2),

	/**
	 * Allow the user to both Pull from the start, from the end to refresh.
	 */
	BOTH(0x3),

	/**
	 * Disables Pull-to-Refresh gesture handling, but allows manually
	 * setting the Refresh state via
	 * {@link PullToRefreshBase#setRefreshing() setRefreshing()}.
	 */
	MANUAL_REFRESH_ONLY(0x4);

	/**
	 * @deprecated Use {@link #PULL_FROM_START} from now on.
	 */
	public static Mode PULL_DOWN_TO_REFRESH = Mode.PULL_FROM_START;

	/**
	 * @deprecated Use {@link #PULL_FROM_END} from now on.
	 */
	public static Mode PULL_UP_TO_REFRESH = Mode.PULL_FROM_END;

	/**
	 * Maps an int to a specific mode. This is needed when saving state, or
	 * inflating the view from XML where the mode is given through a attr
	 * int.
	 * 
	 * @param modeInt
	 *            - int to map a Mode to
	 * @return Mode that modeInt maps to, or PULL_FROM_START by default.
	 */
	static Mode mapIntToValue(final int modeInt) {
	    for (Mode value : Mode.values()) {
		if (modeInt == value.getIntValue()) {
		    return value;
		}
	    }

	    // If not, return default
	    return getDefault();
	}

	static Mode getDefault() {
	    return PULL_FROM_START;
	}

	private int mIntValue;

	// The modeInt values need to match those from attrs.xml
	Mode(int modeInt) {
	    mIntValue = modeInt;
	}

	/**
	 * @return true if the mode permits Pull-to-Refresh
	 */
	boolean permitsPullToRefresh() {
	    return !(this == DISABLED || this == MANUAL_REFRESH_ONLY);
	}

	/**
	 * @return true if this mode wants the Loading Layout Header to be shown
	 */
	public boolean showHeaderLoadingLayout() {
	    return this == PULL_FROM_START || this == BOTH;
	}

	/**
	 * @return true if this mode wants the Loading Layout Footer to be shown
	 */
	public boolean showFooterLoadingLayout() {
	    return this == PULL_FROM_END || this == BOTH
		    || this == MANUAL_REFRESH_ONLY;
	}

	int getIntValue() {
	    return mIntValue;
	}

    }

    // ===========================================================
    // Inner, Anonymous Classes, and Enumerations
    // ===========================================================

    /**
     * Simple Listener that allows you to be notified when the user has scrolled
     * to the end of the AdapterView. See (
     * {@link PullToRefreshAdapterViewBase#setOnLastItemVisibleListener}.
     * 
     * @author Chris Banes
     */
    public static interface OnLastItemVisibleListener {

	/**
	 * Called when the user has scrolled to the end of the list
	 */
	public void onLastItemVisible();

    }

    /**
     * Listener that allows you to be notified when the user has started or
     * finished a touch event. Useful when you want to append extra UI events
     * (such as sounds). See (
     * {@link PullToRefreshAdapterViewBase#setOnPullEventListener}.
     * 
     * @author Chris Banes
     */
    public static interface OnPullEventListener<V extends View> {

	/**
	 * Called when the internal state has been changed, usually by the user
	 * pulling.
	 * 
	 * @param refreshView
	 *            - View which has had it's state change.
	 * @param state
	 *            - The new state of View.
	 * @param direction
	 *            - One of {@link Mode#PULL_FROM_START} or
	 *            {@link Mode#PULL_FROM_END} depending on which direction
	 *            the user is pulling. Only useful when <var>state</var> is
	 *            {@link State#PULL_TO_REFRESH} or
	 *            {@link State#RELEASE_TO_REFRESH}.
	 */
	public void onPullEvent(final PullToRefreshBase<V> refreshView,
		State state, Mode direction);

    }

    /**
     * Simple Listener to listen for any callbacks to Refresh.
     * 
     * @author Chris Banes
     */
    public static interface OnRefreshListener<V extends View> {

	/**
	 * onRefresh will be called for both a Pull from start, and Pull from
	 * end
	 */
	public void onRefresh(final PullToRefreshBase<V> refreshView);

    }

    /**
     * An advanced version of the Listener to listen for callbacks to Refresh.
     * This listener is different as it allows you to differentiate between Pull
     * Ups, and Pull Downs.
     * 
     * @author Chris Banes
     */
    public static interface OnRefreshListener2<V extends View> {
	// TODO These methods need renaming to START/END rather than DOWN/UP

	/**
	 * onPullDownToRefresh will be called only when the user has Pulled from
	 * the start, and released.
	 */
	public void onPullDownToRefresh(final PullToRefreshBase<V> refreshView);

	/**
	 * onPullUpToRefresh will be called only when the user has Pulled from
	 * the end, and released.
	 */
	public void onPullUpToRefresh(final PullToRefreshBase<V> refreshView);

    }

    public static enum Orientation {
	VERTICAL, HORIZONTAL;
    }

    public static enum State {

	/**
	 * When the UI is in a state which means that user is not interacting
	 * with the Pull-to-Refresh function.
	 */
	RESET(0x0),

	/**
	 * When the UI is being pulled by the user, but has not been pulled far
	 * enough so that it refreshes when released.
	 */
	PULL_TO_REFRESH(0x1),

	/**
	 * When the UI is being pulled by the user, and <strong>has</strong>
	 * been pulled far enough so that it will refresh when released.
	 */
	RELEASE_TO_REFRESH(0x2),

	/**
	 * When the UI is currently refreshing, caused by a pull gesture.
	 */
	REFRESHING(0x8),

	/**
	 * When the UI is currently refreshing, caused by a call to
	 * {@link PullToRefreshBase#setRefreshing() setRefreshing()}.
	 */
	MANUAL_REFRESHING(0x9),

	/**
	 * When the UI is currently overscrolling, caused by a fling on the
	 * Refreshable View.
	 */
	OVERSCROLLING(0x10);

	/**
	 * Maps an int to a specific state. This is needed when saving state.
	 * 
	 * @param stateInt
	 *            - int to map a State to
	 * @return State that stateInt maps to
	 */
	static State mapIntToValue(final int stateInt) {
	    for (State value : State.values()) {
		if (stateInt == value.getIntValue()) {
		    return value;
		}
	    }

	    // If not, return default
	    return RESET;
	}

	private int mIntValue;

	State(int intValue) {
	    mIntValue = intValue;
	}

	int getIntValue() {
	    return mIntValue;
	}
    }

    final class SmoothScrollRunnable implements Runnable {
	private final Interpolator mInterpolator;
	private final int mScrollToY;
	private final int mScrollFromY;
	private final long mDuration;
	private OnSmoothScrollFinishedListener mListener;

	private boolean mContinueRunning = true;
	private long mStartTime = -1;
	private int mCurrentY = -1;

	public SmoothScrollRunnable(int fromY, int toY, long duration,
		OnSmoothScrollFinishedListener listener) {
	    mScrollFromY = fromY;
	    mScrollToY = toY;
	    mInterpolator = mScrollAnimationInterpolator;
	    mDuration = duration;
	    mListener = listener;
	}

	@Override
	public void run() {

	    /**
	     * Only set mStartTime if this is the first time we're starting,
	     * else actually calculate the Y delta
	     */
	    if (mStartTime == -1) {
		mStartTime = System.currentTimeMillis();
	    } else {

		/**
		 * We do do all calculations in long to reduce software float
		 * calculations. We use 1000 as it gives us good accuracy and
		 * small rounding errors
		 */
		long normalizedTime = (1000 * (System.currentTimeMillis() - mStartTime))
			/ mDuration;
		normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0);

		final int deltaY = Math.round((mScrollFromY - mScrollToY)
			* mInterpolator
				.getInterpolation(normalizedTime / 1000f));
		mCurrentY = mScrollFromY - deltaY;
		setHeaderScroll(mCurrentY);
	    }

	    // If we're not at the target Y, keep going...
	    if (mContinueRunning && mScrollToY != mCurrentY) {
		ViewCompat.postOnAnimation(PullToRefreshBase.this, this);
	    } else {
		if (null != mListener) {
		    mListener.onSmoothScrollFinished();
		}
	    }
	}

	public void stop() {
	    mContinueRunning = false;
	    removeCallbacks(this);
	}
    }

    static interface OnSmoothScrollFinishedListener {
	void onSmoothScrollFinished();
    }

}
